using System;
using System.IO;
using System.Linq;
using System.Text;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Utilities.IO;

namespace Flare.Crypt
{
	public class PGPCrypt
	{
		#region GET/SET Keys
		public PgpPublicKey PublicKey { get; private set; }
		public PgpPrivateKey PrivateKey { get; private set; }
		public PgpSecretKey SecretKey { get; private set; }
		public int BufferSize { get; private set; }
		#endregion
		
		public PGPCrypt(string public_key_path, string priv_key_path, string pass)
		{
			if(!File.Exists(public_key_path))
			{
				throw new ArgumentException("Public Key File Not Found!", public_key_path);
			}
			if(!File.Exists(priv_key_path))
			{
				throw new ArgumentException("Private Key File Not Found!", priv_key_path);
			}
			if(String.IsNullOrEmpty(pass))
			{
				throw new ArgumentException("Passphrase invalid", pass);	
			}
			// Extract keys from keyrings and store it into the class variables for further use
			PublicKey = GetPublicKey(public_key_path);
		}
		#region FindSecretKey
		internal static PgpPrivateKey FindSecretKey(PgpSecretKeyRingBundle pgpSec, long keyID, char[] pass)
		{
			PgpSecretKey pgpSecKey = pgpSec.GetSecretKey(keyID);

			if (pgpSecKey == null)
			{
				return null;
			}

			return pgpSecKey.ExtractPrivateKey(pass);
		}
		#endregion
		
        #region Public Key
        public PgpPublicKey GetPublicKey(string publicKeyPath)
        {
            using (Stream keyIn = File.OpenRead(publicKeyPath))
            using (Stream inputStream = PgpUtilities.GetDecoderStream(keyIn))
            {
                PgpPublicKeyRingBundle publicKeyRingBundle = new PgpPublicKeyRingBundle(inputStream);
                PgpPublicKey foundKey = GetFirstPublicKey(publicKeyRingBundle);
                if (foundKey != null)
                    return foundKey;
            }
            throw new ArgumentException("No encryption key found in public key ring.");
        }

        public PgpPublicKey GetFirstPublicKey(PgpPublicKeyRingBundle publicKeyRingBundle)
        {
            foreach (PgpPublicKeyRing kRing in publicKeyRingBundle.GetKeyRings())
            {
                PgpPublicKey key = kRing.GetPublicKeys()
                    .Cast<PgpPublicKey>()
                    .Where(k => k.IsEncryptionKey)
                    .FirstOrDefault();
                if (key != null)
                    return key;
            }
            return null;
        }
		#endregion
		
		#region Encrypt_ONLY
		
		
		#endregion
		
		#region Encrypt Byte_array
		public byte[] Encrypt(
            byte[]						clearData,
            string						Keyfile,
            string						fileName,
            bool						armor)
        {
            if (fileName == null)
            {
                fileName = PgpLiteralData.Console;
            }
			
			if (Keyfile == null)
			{
				throw new ArgumentNullException("Keyfile not found! Argument was NULL");	
			}
			
			PgpPublicKey enckey = GetPublicKey(Keyfile);

			MemoryStream bOut = new MemoryStream();

			Stream output = bOut;
			if (armor)
			{
				output = new ArmoredOutputStream(output);
			}
			
			PgpEncryptedDataGenerator encGen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes128, new SecureRandom());
            encGen.AddMethod(enckey);
			Stream encOut = encGen.Open(output, clearData.Length);

			encOut.Write(clearData, 0, clearData.Length);
			encOut.Close();
			
			if (armor)
			{
				output.Close();
			}

			return bOut.ToArray();
        }
		#endregion
		
		#region Compress Byte_array
		private static byte[] Compress(byte[] clearData, string fileName, CompressionAlgorithmTag algorithm)
		{
            MemoryStream bOut = new MemoryStream();

            PgpCompressedDataGenerator comData = new PgpCompressedDataGenerator(algorithm);
            Stream cos = comData.Open(bOut); // open it with the final destination
            PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator();

            // we want to Generate compressed data. This might be a user option later,
            // in which case we would pass in bOut.
            Stream pOut = lData.Open(
				cos,					// the compressed output stream
                PgpLiteralData.Binary,
                fileName,				// "filename" to store
                clearData.Length,		// length of clear data
                DateTime.UtcNow			// current time
            );

			pOut.Write(clearData, 0, clearData.Length);
			pOut.Close();

			comData.Close();

			return bOut.ToArray();
		}
		#endregion
		
		#region GetAsciiString
		private static string GetAsciiString(byte[] bs)
		{
			return Encoding.ASCII.GetString(bs, 0, bs.Length);
		}
		#endregion
		
		#region Decrypt_ByteArray
		public byte[] Decrypt(
	            byte[] encrypted,
				string skeyfile,
	            char[] passPhrase)
	        {
				if(skeyfile == null)
				{	
					throw new ArgumentNullException("Keyfile is null!");
				}
				
				Stream keyIn = File.OpenRead(skeyfile); 
	            Stream inputStream = new MemoryStream(encrypted);
	
	            inputStream = PgpUtilities.GetDecoderStream(inputStream);
	
	            PgpObjectFactory pgpF = new PgpObjectFactory(inputStream);
	            PgpEncryptedDataList enc = null;
	            PgpObject o = pgpF.NextPgpObject();
	
				//
	            // the first object might be a PGP marker packet.
	            //
	            if (o is PgpEncryptedDataList)
	            {
	                enc = (PgpEncryptedDataList) o;
	            }
	            else
	            {
	                enc = (PgpEncryptedDataList) pgpF.NextPgpObject();
	            }
	
				PgpPrivateKey sKey = null;
				PgpPublicKeyEncryptedData pbe = null;
				PgpSecretKeyRingBundle pgpsec = new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(keyIn));
		
				foreach (PgpPublicKeyEncryptedData pked in enc.GetEncryptedDataObjects())
                {
                    sKey = FindSecretKey(pgpsec, pked.KeyId, passPhrase);

                    if (sKey != null)
                    {
                        pbe = pked;
                        break;
                    }
                }
			
				if (sKey == null)
                {
                    throw new ArgumentException("secret key for message not found.");
                }
				
	
				Stream clear = pbe.GetDataStream(sKey);
	
				return Streams.ReadAll(clear);
	        }
			#endregion
	}
}

